﻿#include "FITKGraphObjectSketch2DPreview.h"

// VTK
#include <vtkProperty.h>
#include <vtkPoints.h>
#include <vtkUnstructuredGrid.h>
#include <vtkAbstractCellLocator.h>
#include <vtkGenericCell.h>
#include <vtkMath.h>
#include <vtkInteractorObserver.h>
#include <vtkTransform.h>

// Graph
#include "FITKGraphCommons.h"
#include "FITK_Interface/FITKVTKAlgorithm/FITKGraphActor.h"

// Data
#include "FITK_Interface/FITKInterfaceGeometry/FITKAbsGeoSketch2D.h"
#include "FITK_Interface/FITKInterfaceGeometry/FITKVirtualTopoManager.h"
#include "FITK_Interface/FITKInterfaceGeometry/FITKGeometryMeshVS.h"
#include "FITK_Interface/FITKInterfaceGeometry/FITKGeometryMeshEntity.h"

// Render
#include "FITK_Component/FITKRenderWindowVTK/FITKGraphRender.h"

namespace Exchange
{
    FITKGraphObjectSketch2DPreview::FITKGraphObjectSketch2DPreview(Interface::FITKAbsGeoSketch2D* sketchData)
        : FITKGraphObject3D(nullptr, sketchData)
    {
        if (!sketchData)
        {
            return;
        }

        // Initialize.
        init();

        // Convert shape data.
        generateDataSet();

        // Generate the shape actor.
        generateGraph();

        // Set the layer need to be rendered.
        setRenderLayer(1, 2);

        // Save the data type.
        m_graphInfo.Type = GraphDataType::OtherGraph;

        // Save if the bounds are fixed or dynamic.
        m_hasFixedBounds = true;
    }

    FITKGraphObjectSketch2DPreview::~FITKGraphObjectSketch2DPreview()
    {
        // Delete the vtk objects.
        deleteVtkObj(m_ugridEdge);
        deleteVtkObj(m_pointsEdge);
        deleteVtkObj(m_ugridVert);
        deleteVtkObj(m_pointsVert);
        deleteVtkObj(m_ugridCons);
        deleteVtkObj(m_pointsCons);
    }

    void FITKGraphObjectSketch2DPreview::init()
    {
        // Create the sketch grid.
        m_pointsEdge = vtkPoints::New();
        m_ugridEdge = vtkUnstructuredGrid::New();
        m_ugridEdge->SetPoints(m_pointsEdge);

        m_pointsVert = vtkPoints::New();
        m_ugridVert = vtkUnstructuredGrid::New();
        m_ugridVert->SetPoints(m_pointsVert);

        // Create the constructions grid.
        m_pointsCons = vtkPoints::New();
        m_ugridCons = vtkUnstructuredGrid::New();
        m_ugridCons->SetPoints(m_pointsCons);
    }

    void FITKGraphObjectSketch2DPreview::update(bool forceUpdate)
    {
        Interface::FITKAbsGeoSketch2D* sketchCmd = dynamic_cast<Interface::FITKAbsGeoSketch2D*>(_dataObj);
        if (!sketchCmd)
        {
            return;
        }

        Interface::FITKAbsGeoShapeAgent* shapeAgent = sketchCmd->getShapeAgent();
        Interface::FITKConstructionManager* consMgr = sketchCmd->getConstructionManager();
        if (!shapeAgent || !consMgr)
        {
            return;
        }

        // Check the sketch state.
        QVariant newState;
        shapeAgent->createShapeState(newState);
        int nCons = consMgr->getDataCount();

        if (m_shapeState == newState && m_numCons == nCons && !forceUpdate)
        {
            return;
        }

        // Reset data.
        clearData();

        // Re-generate the dataset.
        generateDataSet();

        // Update the pipeline.
        if (m_fActorEdge)
        {
            m_fActorEdge->update();
        }

        if (m_fActorVert)
        {
            m_fActorVert->update();
        }

        if (m_fActorCons)
        {
            m_fActorCons->update();
        }
    }

    bool FITKGraphObjectSketch2DPreview::getFixedBounds(double* bounds)
    {
        if (!m_fActorEdge || !m_fActorVert || !m_ugridEdge || !m_ugridVert)
        {
            return false;
        }

        bool hasValidGeom = false;

        double bds6[6]{ VTK_DOUBLE_MAX, -VTK_DOUBLE_MAX, VTK_DOUBLE_MAX,
            -VTK_DOUBLE_MAX, VTK_DOUBLE_MAX, -VTK_DOUBLE_MAX };

        // Check the actor and dataset.
        if (m_fActorEdge->GetVisibility())
        {
            if (m_ugridEdge->GetNumberOfPoints() != 0)
            {
                // Get the bounds.
                double* bds = m_fActorEdge->GetBounds();

                for (int i = 0; i < 3; i++)
                {
                    bds6[i * 2] = qMin(bds6[i * 2], bds[i * 2]);
                    bds6[i * 2 + 1] = qMax(bds6[i * 2 + 1], bds[i * 2 + 1]);
                }

                hasValidGeom = true;
            }
        }     

        if (m_fActorVert->GetVisibility())
        {
            if (m_ugridVert->GetNumberOfPoints() != 0)
            {
                // Get the bounds.
                double* bds = m_fActorVert->GetBounds();

                for (int i = 0; i < 3; i++)
                {
                    bds6[i * 2] = qMin(bds6[i * 2], bds[i * 2]);
                    bds6[i * 2 + 1] = qMax(bds6[i * 2 + 1], bds[i * 2 + 1]);
                }

                hasValidGeom = true;
            }
        }

        // Copy the bounds.
        if (hasValidGeom)
        {
            for (int i = 0; i < 6; i++)
            {
                bounds[i] = bds6[i];
            }
        }

        return hasValidGeom;
    }

    bool FITKGraphObjectSketch2DPreview::generateDataSet()
    {
        Interface::FITKAbsGeoSketch2D* sketchCmd = dynamic_cast<Interface::FITKAbsGeoSketch2D*>(_dataObj);
        if (!sketchCmd)
        {
            return false;
        }

        int ptIndex = -1, cellIndex = -1;

        // Get all sub-commands.
        int nSubSketch = sketchCmd->getDataCount();
        for (int i = 0; i < nSubSketch; i++)
        {
            Interface::FITKAbsGeoCommand* subCmd = sketchCmd->getDataByIndex(i);
            if (!subCmd)
            {
                return false;
            }

            // Get the abstract shape agent.
            Interface::FITKAbsGeoShapeAgent* shapeAgent = subCmd->getShapeAgent();
            if (!shapeAgent)
            {
                return false;
            }

            // Get the trianglation of the shape.
            Interface::FITKGeometryMeshVS* meshVS = shapeAgent->getMeshVS();
            Interface::FITKVirtualTopoManager* topoMgr = shapeAgent->getVirtualTopoManager();
            if (!meshVS || !topoMgr)
            {
                return false;
            }

            // Get all edges.
            const QHash<int, Interface::FITKGeoMeshVSEdgeEntity*> edgesHash = meshVS->getEdgeItems();
            QHashIterator<int, Interface::FITKGeoMeshVSEdgeEntity*> iterEdge(edgesHash);

            while (iterEdge.hasNext())
            {
                iterEdge.next();

                Interface::FITKGeoMeshVSEdgeEntity* edge = iterEdge.value();

                // Get edge points.
                const QList<Interface::FITKGeoMeshVSPt*> & pts = edge->getPoints();

                // Get the node data and create poly line.
                vtkSmartPointer<vtkIdList> polyLineCell = vtkSmartPointer<vtkIdList>::New();

                for (Interface::FITKGeoMeshVSPt* pt : pts)
                {
                    // Add point.
                    ptIndex = m_pointsEdge->InsertNextPoint(pt->x(), pt->y(), pt->z());
                    polyLineCell->InsertNextId(ptIndex);
                }

                // Add cell.
                cellIndex = m_ugridEdge->InsertNextCell(VTKCellType::VTK_POLY_LINE, polyLineCell);
            }

            // Get all points.
            const QHash<int, Interface::FITKGeoMeshVSPt*> pointsHash = meshVS->getPointItems();
            QHashIterator<int, Interface::FITKGeoMeshVSPt*> iterVert(pointsHash);

            while (iterVert.hasNext())
            {
                iterVert.next();

                Interface::FITKGeoMeshVSPt* pt = iterVert.value();

                // Add point.
                ptIndex = m_pointsVert->InsertNextPoint(pt->x(), pt->y(), pt->z());

                // Add vertex cell.
                vtkSmartPointer<vtkIdList> cell = vtkSmartPointer<vtkIdList>::New();
                cell->InsertNextId(ptIndex);
                m_ugridVert->InsertNextCell(VTKCellType::VTK_VERTEX, cell);
            }
        }

        // Generate constructions.
        Interface::FITKAbsConstruction* currCons = sketchCmd->getCurrentConstruction();
        if (!currCons)
        {
            return true;
        }

        // Get the sketch plane.
        double pPos[3], pNor[3], pUp[3];
        sketchCmd->getPlane(pPos, pNor, pUp);

        // Get the segment length by the sketch size.
        double size = sketchCmd->getDefaultSize();
        double segLen = size / 200.;

        double pos1[3], pos2[3];
        currCons->getPositions(pos1, pos2);

        // Get the distance between two positions.
        double length = sqrt(pow(pos1[0] - pos2[0], 2) + pow(pos1[1] - pos2[1], 2) + pow(pos1[2] - pos2[2], 2));

        int nSeg = 0;
        int nSegForSnipping = 0;

        Interface::FITKGeoEnum::FITKConstructionType type = currCons->getConstructionType();
        switch (type)
        {
        case Interface::FITKGeoEnum::FCTLine:
        {
            // Get the number of the segments for visualization.
            nSeg = int(length / segLen);
            nSegForSnipping = nSeg;

            FITKGraphCommons::AddStippleLine(m_ugridCons, pos1, pos2, nSeg);
            break;
        }
        case Interface::FITKGeoEnum::FCTCircle:
        {
            // Get the number of the segments for visualization.
            double per = 2 * length * 3.1415926;
            nSeg = int(per / segLen);
            nSegForSnipping = nSeg + 1;

            // Draw the circle.
            double degStep = 360. / nSeg;

            vtkSmartPointer<vtkTransform> transform = vtkSmartPointer<vtkTransform>::New();
            transform->RotateWXYZ(degStep, pNor);
            transform->Translate(-pos1[0], -pos1[1], -pos1[2]);

            double outPt[3]{ pos2[0] - pos1[0], pos2[1] - pos1[1], pos2[2] - pos1[2] };
            int nPts = nSeg + 1;
            for (int i = 0; i < nPts; i++)
            {
                // Rotate and add the point.
                double* newOutPos = transform->TransformDoublePoint(outPt);
                outPt[0] = newOutPos[0] + pos1[0];
                outPt[1] = newOutPos[1] + pos1[1];
                outPt[2] = newOutPos[2] + pos1[2];
                m_pointsCons->InsertNextPoint(outPt);

                // Add stipple line segment.
                if (i % 2 == 0)
                {
                    vtkSmartPointer<vtkIdList> cell = vtkSmartPointer<vtkIdList>::New();
                    cell->InsertNextId(i);
                    cell->InsertNextId((i + 1) % nPts);
                    m_ugridCons->InsertNextCell(VTKCellType::VTK_LINE, cell);
                }
            }

            break;
        }
        default:
            break;
        }

        return true;
    }

    void FITKGraphObjectSketch2DPreview::generateGraph()
    {
        // Get the sketch preview color.
        double color3[3]{ 0., 0., 0. };
        FITKGraphCommons::QColorToDouble3(Qt::red, color3);

        // Create the edge actor.
        m_fActorEdge = FITKGraphActor::New();
        m_fActorEdge->setGraphObject(this);
        m_fActorEdge->setActorType(ActorType::EdgeActor);
        m_fActorEdge->SetPickable(false);
        m_fActorEdge->SetVisibility(true);
        m_fActorEdge->GetProperty()->SetRepresentation(1);
        m_fActorEdge->GetProperty()->SetColor(color3);
        m_fActorEdge->GetProperty()->SetLineWidth(2);
        m_fActorEdge->GetProperty()->SetVertexVisibility(false);
        addActor(m_fActorEdge);

        // Create the vertex actor.
        m_fActorVert = FITKGraphActor::New();
        m_fActorVert->setGraphObject(this);
        m_fActorVert->setActorType(ActorType::VertexActor);
        m_fActorVert->SetPickable(false);
        m_fActorVert->SetVisibility(true);
        m_fActorVert->GetProperty()->SetRepresentation(1);
        m_fActorVert->GetProperty()->SetColor(color3);
        m_fActorVert->GetProperty()->SetLineWidth(2);
        m_fActorVert->GetProperty()->SetPointSize(5);
        m_fActorVert->GetProperty()->SetVertexVisibility(true);
        addActor(m_fActorVert);

        // Create the constructions actor.
        m_fActorCons = FITKGraphActor::New();
        m_fActorCons->setGraphObject(this);
        m_fActorCons->setActorType(ActorType::EdgeActor);
        m_fActorCons->SetPickable(false);
        m_fActorCons->SetVisibility(true);
        m_fActorCons->GetProperty()->SetRepresentation(1);
        m_fActorCons->GetProperty()->SetColor(color3);
        m_fActorCons->GetProperty()->SetLineWidth(2);
        m_fActorCons->GetProperty()->SetVertexVisibility(false);
        addActor(m_fActorCons);

        // Set the input dataset.
        m_fActorEdge->setInputDataObject(m_ugridEdge);
        m_fActorVert->setInputDataObject(m_ugridVert);
        m_fActorCons->setInputDataObject(m_ugridCons);

        // Set the input dataset.
        m_fActorEdge->setRelativeCoincidentTopologyPolygonOffsetParameters(-2);
        m_fActorVert->setRelativeCoincidentTopologyPolygonOffsetParameters(-2);
        m_fActorCons->setRelativeCoincidentTopologyPolygonOffsetParameters(-2);
    }

    void FITKGraphObjectSketch2DPreview::clearData()
    {
        // Reset the vtk data.
        resetVtkObj(m_ugridEdge);
        resetVtkObj(m_pointsEdge);
        resetVtkObj(m_ugridVert);
        resetVtkObj(m_pointsVert);       
        resetVtkObj(m_ugridCons);
        resetVtkObj(m_pointsCons);

        m_shapeState = QVariant();
        m_numCons = 0;
    }
}   // namespace Exchange